import axios from "axios";

const __THROTTLE_MAP__: Axios.Throttle = new Map();

function cache(config: Axios.RequestConfig) {
  const key = "Cache-Control";
  const cache = typeof config.cache === "number" ? Math.abs(config.cache) : 0;
  if (cache) {
    config.headers[key] = "public,max-age=" + cache;
  } else {
    config.headers[key] = "no-cache,no-store,max-age=0,must-revalidate";
  }
}

function throttle(config?: Axios.RequestConfig) {
  const map = __THROTTLE_MAP__;
  const hash = generateHash();

  function generateHash() {
    if (config) {
      const { url, method, data, params } = config;
      let hash = "";
      if (method) {
        hash += `${method}`;
      }
      if (url) {
        hash += `:${url}`;
      }
      if (data) {
        hash += `:${JSON.stringify(data)}`;
      }
      if (params) {
        hash += `:${JSON.stringify(params)}`;
      }
      return hash;
    } else {
      return "";
    }
  }

  return {
    start(request: Axios.InterceptorRequest<any>) {
      if (config?.throttle && map.has(hash)) {
        const cancel = map.get(hash)!;
        cancel(`接口[${hash}]频繁请求`, config, request);
      }
      if (config?.throttle) {
        const source = axios.CancelToken.source();
        config.cancelToken = source.token;
        map.set(hash, source.cancel);
      }
    },
    delete() {
      if (config?.throttle && map.has(hash)) {
        map.delete(hash);
      }
    },
  };
}

function responseData(response: Axios.Response) {
  if (response.config.responseData) {
    return response.data;
  } else {
    return response;
  }
}

export function createAxios(config: Axios.DefaultConfig = {}) {
  const http = axios.create(axios.mergeConfig({ timeout: 15000 }, config));

  const methods: {
    get: Axios.Method;
    put: Axios.Method;
    post: Axios.Method;
    patch: Axios.Method;
    delete: Axios.Method;
  } = {
    get<T = any, D = any>(
      url: string,
      params?: D,
      config: Axios.DefaultConfig<D> = {}
    ) {
      config = axios.mergeConfig({ params }, config);
      if (config?.responseData) {
        return http.get<T, Axios.Response<T, D>, D>(url, config);
      } else {
        return http.get<T, T, D>(url, config);
      }
    },
    put<T = any, D = any>(
      url: string,
      data?: D,
      config: Axios.DefaultConfig<D> = {}
    ) {
      if (config?.responseData) {
        return http.put<T, Axios.Response<T, D>, D>(url, data, config);
      } else {
        return http.put<T, T, D>(url, data, config);
      }
    },
    post<T = any, D = any>(
      url: string,
      data?: D,
      config: Axios.DefaultConfig<D> = {}
    ) {
      if (config?.responseData) {
        return http.post<T, Axios.Response<T, D>, D>(url, data, config);
      } else {
        return http.post<T, T, D>(url, data, config);
      }
    },
    patch<T = any, D = any>(
      url: string,
      data?: D,
      config: Axios.DefaultConfig<D> = {}
    ) {
      if (config?.responseData) {
        return http.patch<T, Axios.Response<T, D>, D>(url, data, config);
      } else {
        return http.patch<T, T, D>(url, data, config);
      }
    },
    delete<T = any, D = any>(
      url: string,
      params?: D,
      config: Axios.DefaultConfig<D> = {}
    ) {
      config = axios.mergeConfig({ params }, config);
      if (config?.responseData) {
        return http.delete<T, Axios.Response<T, D>, D>(url, config);
      } else {
        return http.delete<T, T, D>(url, config);
      }
    },
  };

  const request = http.interceptors.request;

  const response = http.interceptors.response;

  /**
   * 初始化拦截器
   */
  interceptor((request, response) => {
    request.use(
      (config) => {
        cache(config);
        throttle(config).start(request);
        return config;
      },
      (error) => {
        throttle(error.config).delete();
        return Promise.reject(error);
      }
    );
    response.use(
      (response) => {
        throttle(response.config).delete();
        return responseData(response);
      },
      (error) => {
        throttle(error.config).delete();
        return Promise.reject(error);
      }
    );
  });

  /**
   * 拦截器
   */
  function interceptor<T = any, D = any>(
    callback: (
      request: Axios.InterceptorRequest<D>,
      response: Axios.InterceptorResponse<T, D>
    ) => void
  ) {
    if (typeof callback === "function") callback(request, response);
    return { request, response };
  }

  /**
   * 请求拦截器
   */
  function interceptorRequest<D = any>(
    callback: (request: Axios.InterceptorRequest<D>) => void
  ) {
    if (typeof callback === "function") callback(request);
    return request;
  }

  /**
   * 响应拦截器
   * */
  function interceptorResponse<T = any, D = any>(
    callback: (response: Axios.InterceptorResponse<T, D>) => void
  ) {
    if (typeof callback === "function") callback(response);
    return response;
  }

  /**
   * 监听拦截器
   */
  function onInterceptor<T = any, D = any>(
    onRequest: (config: Axios.RequestConfig<D>) => void,
    onResponse: (response: Axios.Response<T, D>) => void,
    onError: (error: Axios.Error<T, D>) => void
  ) {
    interceptor((request, response) => {
      request.use(
        (config) => {
          if (typeof onRequest === "function") {
            onRequest(config);
          }
          return config;
        },
        (error) => {
          if (typeof onError === "function" && !axios.isCancel(error)) {
            onError(error);
          }
          return Promise.reject(error);
        }
      );
      response.use(
        (response) => {
          if (typeof onResponse === "function") {
            onResponse(response);
          }
          return response;
        },
        (error) => {
          if (typeof onError === "function" && !axios.isCancel(error)) {
            onError(error);
          }
          return Promise.reject(error);
        }
      );
    });
  }

  /**
   * 401 错误
   */
  function onInterceptor401<T = any, D = any>(
    callback: (error: Axios.Error<T, D>) => void
  ) {
    return interceptorResponse<T, D>((response) => {
      response.use(null, (error) => {
        if (
          axios.isAxiosError(error) &&
          typeof callback === "function" &&
          error.code === axios.AxiosError.ERR_BAD_REQUEST &&
          error.status === 401
        ) {
          callback(error);
        }
        return Promise.reject(error);
      });
    });
  }

  /**
   * 404 错误
   */
  function onInterceptor404<T = any, D = any>(
    callback: (error: Axios.Error<T, D>) => void
  ) {
    return interceptorResponse<T, D>((response) => {
      response.use(null, (error) => {
        if (
          axios.isAxiosError(error) &&
          typeof callback === "function" &&
          error.code === axios.AxiosError.ERR_BAD_REQUEST &&
          error.status === 404
        ) {
          callback(error);
        }
        return Promise.reject(error);
      });
    });
  }

  /**
   * 500 错误
   */
  function onInterceptor500<T = any, D = any>(
    callback: (error: Axios.Error<T, D>) => void
  ) {
    return interceptorResponse<T, D>((response) => {
      response.use(null, (error) => {
        if (
          axios.isAxiosError(error) &&
          typeof callback === "function" &&
          error.code === axios.AxiosError.ERR_BAD_RESPONSE &&
          error.status === 500
        ) {
          callback(error);
        }
        return Promise.reject(error);
      });
    });
  }

  /**
   * 超时错误
   */
  function onInterceptorTimeout<T = any, D = any>(
    callback: (error: Axios.Error<T, D>) => void
  ) {
    return interceptorResponse<T, D>((response) => {
      response.use(null, (error) => {
        if (
          axios.isAxiosError(error) &&
          typeof callback === "function" &&
          error.code === axios.AxiosError.ECONNABORTED
        ) {
          callback(error);
        }
        return Promise.reject(error);
      });
    });
  }

  /**
   * 错误网络
   */
  function onInterceptorNetwork<T = any, D = any>(
    callback: (error: Axios.Error<T, D>) => void
  ) {
    return interceptorResponse<T, D>((response) => {
      response.use(null, (error) => {
        if (
          axios.isAxiosError(error) &&
          typeof callback === "function" &&
          error.code === axios.AxiosError.ERR_NETWORK
        ) {
          callback(error);
        }
        return Promise.reject(error);
      });
    });
  }

  /**
   * 设置请求头
   */
  function setRequestHeaders<D = any>(
    callback: (
      config: Axios.RequestConfig<D>
    ) => Data<string | number | null | undefined>
  ) {
    return interceptorRequest<D>((request) => {
      request.use((config) => {
        if (typeof callback === "function") {
          const headers = callback(config);
          for (const name in headers) {
            config.headers[name] = headers[name];
          }
        }
        return config;
      });
    });
  }

  return Object.assign(methods, {
    interceptor,
    interceptorRequest,
    interceptorResponse,
    onInterceptor,
    onInterceptor401,
    onInterceptor404,
    onInterceptor500,
    onInterceptorTimeout,
    onInterceptorNetwork,
    setRequestHeaders,
  });
}
